package com.koron.common.core.business.common.service.impl;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.service.DefaultFastFileStorageClient;
import com.koron.bean.base.Attachment;
import com.koron.bean.system.staff.vo.EamUser;
import com.koron.common.core.business.common.mapper.AttachmentMapper;
import com.koron.common.core.business.common.service.AttachmentService;
import com.koron.common.core.config.ThreadLocalContext;
import com.koron.common.core.util.CodeTools;
import com.koron.common.core.util.dfs.FastDFSClientWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.ListUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.koron.ebs.mybatis.ADOConnection;
import org.koron.ebs.mybatis.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author qingfeng
 * @description: 附件操作实现类
 * @date 2021/7/7 17:53
 */
@Slf4j
@Service
public class AttachmentServiceImpl implements AttachmentService {

    /**
     * fdfs客户端
     */
    @Autowired
    private FastDFSClientWrapper fdfsCient;

    @Autowired
    private DefaultFastFileStorageClient storageClient;

    private EamUser getCurrUser() {
        return ThreadLocalContext.get();
    }

    /**
     * 上传附件
     * (删除之前业务附件， 适用于业务新增或更新)
     * 1、若传入的附件为空，返回空列表
     * 2、若业务是更新，会先删除先前的业务附件(如果有附件), 故更新时，需要将全部附件上传
     * 3、若有附件上传失败，抛出异常, 事务回滚，删除已上传的附件
     * <p>
     * 当前用户
     *
     * @param businessId 业务id
     * @param files      上传的附件
     * @return 上传成功后的附件, 成功返回附件实体, 失败抛出异常
     */
    @Override
    public List<Attachment> uploadAttachment(String businessId, MultipartFile... files) {
        return uploadAttachmentWithType(businessId, null, files);
    }

    /**
     * 删除之前业务附件， 适用于业务更新
     * <p>
     * 当前用户
     *
     * @param businessId   业务id
     * @param businessType 业务类型
     * @param files        上传的附件
     * @return 上传成功后的附件, 成功返回附件实体, 失败抛出异常
     */
    @Override
    public List<Attachment> uploadAttachmentWithType(String businessId, String businessType, MultipartFile... files) {
        return uploadAttachmentWithTypeWithSubBusinessId(businessId, null, businessType, files);
    }

    /**
     * 删除之前业务附件， 适用于业务更新
     * <p>
     * 当前用户
     *
     * @param businessId    业务id
     * @param subBusinessId 子业务id
     * @param businessType  业务类型
     * @param files         上传的附件
     * @return 上传成功后的附件, 成功返回附件实体, 失败抛出异常
     */
    @Override
    public List<Attachment> uploadAttachmentWithTypeWithSubBusinessId(String businessId, String subBusinessId, String businessType, MultipartFile... files) {
        if (ArrayUtils.isEmpty(files)) {
            return ListUtils.EMPTY_LIST;
        }
        // 已经上传成功的附件
        List<Attachment> uploadedAttachment = new ArrayList<>();
        ADOConnection.runTask(ThreadLocalContext.get().getCurrDs(), factory-> {
            try {
                AttachmentMapper mapper = factory.getMapper(AttachmentMapper.class);
                // 原先的业务附件
                List<Attachment> attachments = mapper.listByBusinessId(businessId, subBusinessId, businessType);
                for (MultipartFile multipartFile : files) {
                    Attachment attachment = generateAttachment(multipartFile, businessId, businessType, subBusinessId);
                    String url = fdfsCient.uploadFile(multipartFile);
                    attachment.setPath(url);
                    uploadedAttachment.add(attachment);
                }
                mapper.batchInsert(uploadedAttachment);
                // 删除原先的业务附件
                String[] ids = new String[attachments.size()];
                attachments.stream().map(row -> row.getId()).collect(Collectors.toList()).toArray(ids);
                mapper.batchDelete(ids);
                attachments.stream().forEach(row -> fdfsCient.deleteFile(row.getPath()));
            } catch (IOException e) {
                uploadedAttachment.stream().forEach(row -> fdfsCient.deleteFile(row.getPath()));
                throw new RuntimeException(e.getMessage());
            }
            return null;
        },void.class);
        return uploadedAttachment;
    }

    /**
     * 上传附件
     * <p>
     * 当前用户
     *
     * @param businessId 业务id
     * @param file       上传的附件
     * @return 上传成功后的附件, 失败则抛出异常，成功返回附件实体
     */
    @Override
    public Attachment uploadSingleAttachment(String businessId, MultipartFile file) {
        return uploadSingleAttachmentWithType(businessId, null, file);
    }

    /**
     * 上传附件
     * <p>
     * 当前用户
     *
     * @param businessId   业务id
     * @param businessType 业务类型
     * @param file         上传的附件
     * @return 上传成功后的附件, 成功返回附件实体, 失败抛出异常
     */
    @Override
    public Attachment uploadSingleAttachmentWithType(String businessId, String businessType, MultipartFile file) {
        return uploadSingleAttachmentWithTypeWithSubBussinessId(businessId, null, businessType, file);
    }

    /**
     * 上传附件
     * <p>
     * 当前用户
     *
     * @param businessId   业务id
     * @param businessType 业务类型
     * @param file         上传的附件
     * @return 上传成功后的附件, 成功返回附件实体, 失败抛出异常
     */
    @Override
    public Attachment uploadSingleAttachmentWithTypeWithSubBussinessId(String businessId, String subBusinessId, String businessType, MultipartFile file) {
        Assert.notNull(file, "附件不能为空");
        Attachment attachment = generateAttachment(file, businessId, businessType, subBusinessId);
        return ADOConnection.runTask(ThreadLocalContext.get().getCurrDs(), factory-> {
            try {
                AttachmentMapper mapper = factory.getMapper(AttachmentMapper.class);
                String url = fdfsCient.uploadFile(file);
                attachment.setPath(url);
                mapper.insert(attachment);
                return attachment;
            } catch (IOException e) {
                fdfsCient.deleteFile(attachment.getPath());
                throw new RuntimeException(e.getMessage());
            }
        },Attachment.class);
    }

    /**
     * 根据业务附件列表
     *
     * @param businessId 业务id
     * @return 附件列表
     */
    @Override
    public List<Attachment> getAttachmentListByBusinessId(String businessId) {
        return getAttachmentListByBusinessIdWithType(businessId, null);
    }

    /**
     * 根据业务id查询附件列表
     *
     * @param businessType
     * @param businessId   业务id
     * @return 附件列表
     */
    @Override
    public List<Attachment> getAttachmentListByBusinessIdWithType(String businessId, String businessType) {
        return getAttachmentListByBusinessIdWithTypeWithSubBusinessId(businessId, null, businessType);
    }

    /**
     * 根据业务id查询附件列表
     *
     * @param businessType
     * @param businessId    业务id
     * @param subBusinessId 子业务id
     * @return 附件列表
     */
    @Override
    public List<Attachment> getAttachmentListByBusinessIdWithTypeWithSubBusinessId(String businessId, String subBusinessId, String businessType) {
        try (SessionFactory factory = new SessionFactory()) {
            factory.setDbEnv(getCurrUser().getCurrDs());
            AttachmentMapper mapper = factory.getMapper(AttachmentMapper.class);
            return mapper.listByBusinessId(businessId, subBusinessId, businessType);
        }
    }

    /**
     * 根据业务ids查询附件列表
     *
     * @param businessType
     * @param businessIds  业务ids
     * @return 附件列表
     */
    @Override
    public List<Attachment> getAttachmentListByBusinessIdsWithType(String[] businessIds, String businessType) {
        try (SessionFactory factory = new SessionFactory()) {
            factory.setDbEnv(getCurrUser().getCurrDs());
            AttachmentMapper mapper = factory.getMapper(AttachmentMapper.class);
            return mapper.listByBusinessIds(businessIds, businessType);
        }
    }

    /**
     * 根据附件id获取附件
     *
     * @param fileId 附件id
     * @return 附件实体
     */
    @Override
    public Attachment getById(String fileId) {
        try (SessionFactory factory = new SessionFactory()) {
            factory.setDbEnv(getCurrUser().getCurrDs());
            AttachmentMapper mapper = factory.getMapper(AttachmentMapper.class);
            return mapper.getById(fileId);
        }
    }

    /**
     * 根据附件的url获取附件
     *
     * @param attachmentUrl 附件url
     * @return 附件实体
     */
    @Override
    public Attachment getByUrl(String attachmentUrl) {
        try (SessionFactory factory = new SessionFactory()) {
            factory.setDbEnv(getCurrUser().getCurrDs());
            AttachmentMapper mapper = factory.getMapper(AttachmentMapper.class);
            return mapper.getByUrl(attachmentUrl);
        }
    }

    @Override
    public int deleteAttachment(String businessId) {
        return deleteAttachmentWithType(businessId, null);
    }

    /**
     * 根据业务id+业务类型删除附件
     *
     * @param businessId   业务id
     * @param businessType
     * @return 影响的行数
     */
    @Override
    public int deleteAttachmentWithType(String businessId, String businessType) {
        return deleteAttachmentWithTypeWithSubBusinessId(businessId, null, businessType);
    }

    /**
     * 根据业务id+子业务id+业务类型删除附件
     *
     * @param businessId    业务id
     * @param subBusinessId 子业务id
     * @param businessType
     * @return 影响的行数
     */
    @Override
    public int deleteAttachmentWithTypeWithSubBusinessId(String businessId, String subBusinessId, String businessType) {
        return ADOConnection.runTask(ThreadLocalContext.get().getCurrDs(), factory-> {
            try {
                AttachmentMapper mapper = factory.getMapper(AttachmentMapper.class);
                List<Attachment> attachments = mapper.listByBusinessId(businessId, subBusinessId, businessType);
                if (CollectionUtils.isEmpty(attachments)) {
                    return 0;
                }
                List<String> ids = attachments.stream().map(p -> p.getId()).collect(Collectors.toList());
                int rowNum = mapper.batchDelete(ids.toArray(new String[]{}));
                attachments.stream().forEach(row -> fdfsCient.deleteFile(row.getPath()));
                return rowNum;
            } catch (Exception e) {
                log.error(e.getMessage(), e);
                throw new RuntimeException(e.getMessage());
            }
        },Integer.class);
    }

    @Override
    public int deleteAttachment(String[] ids) {
        return ADOConnection.runTask(ThreadLocalContext.get().getCurrDs(), factory-> {
            try {
                AttachmentMapper mapper = factory.getMapper(AttachmentMapper.class);
                List<Attachment> attachments = mapper.getListByIds(ids);
                int rowNum = mapper.batchDelete(ids);
                attachments.stream().forEach(row -> fdfsCient.deleteFile(row.getPath()));
                return rowNum;
            } catch (Exception e) {
                log.error(e.getMessage(), e);
                throw new RuntimeException(e.getMessage());
            }
        },Integer.class);
    }

    @Override
    public int deleteByBusinessIdsWithType(String[] businessIds, String businessType) {
        for (String businessId : businessIds) {
            deleteAttachmentWithType(businessId, businessType);
        }
        return businessIds.length;
    }

    /**
     * 追加附件，不会删除原有附件
     * <p>
     * 当前用户
     *
     * @param businessId 业务id
     * @param files      附件
     * @return 附件的个数
     */
    @Override
    public List<Attachment> appendAttachment(String businessId, String[] deleteFileIds, MultipartFile... files) {
        return appendAttachmentWithType(businessId, null, deleteFileIds, files);
    }

    /**
     * 追加附件，不会删除原有附件
     * <p>
     * 当前用户
     *
     * @param businessId   业务id
     * @param businessType
     * @param files        附件
     * @return 附件的个数
     */
    @Override
    public List<Attachment> appendAttachmentWithType(String businessId, String businessType, String[] deleteFileIds, MultipartFile... files) {
        return appendAttachmentWithTypeWithSubBusinessId(businessId, null, businessType, deleteFileIds, files);
    }

    /**
     * 追加附件，不会删除原有附件
     * <p>
     * 当前用户
     *
     * @param businessId    业务id
     * @param subBusinessId 子业务id
     * @param businessType
     * @param files         附件
     * @return 附件的个数
     */
    @Override
    public List<Attachment> appendAttachmentWithTypeWithSubBusinessId(String businessId, String subBusinessId, String businessType, String[] deleteFileIds, MultipartFile... files) {
        if (ArrayUtils.isEmpty(files)) {
            return null;
        }
        return ADOConnection.runTask(ThreadLocalContext.get().getCurrDs(), factory-> {
            try {
                List<Attachment> list = new ArrayList<>();
                for (MultipartFile multipartFile : files) {
                    Attachment attachment = generateAttachment(multipartFile, businessId, businessType, subBusinessId);
                    attachment.setPath(fdfsCient.uploadFile(multipartFile));
                    list.add(attachment);
                }
                AttachmentMapper mapper = factory.getMapper(AttachmentMapper.class);
                int rowNum = mapper.batchInsert(list);
                log.info("插入新附件数量：{}",rowNum);

                if (!ArrayUtils.isEmpty(deleteFileIds) && deleteFileIds.length > 0) {
                    List<Attachment> attachments = mapper.getListByIds(deleteFileIds);
                    int rowNumDel = mapper.batchDelete(deleteFileIds);
                    log.info("删除原附件数量：{}",rowNumDel);
                    for (Attachment row : attachments) {
                        fdfsCient.deleteFile(row.getPath());
                    }
                }
                return list;
            } catch (Exception e) {
                log.error(e.getMessage(), e);
                throw new RuntimeException(e.getMessage());
            }
        },List.class);
    }

    @Override
    public int copyAttachment(String businessId, String copyBusinessId) {
        return copyAttachmentWithType(businessId, null, copyBusinessId);
    }

    /**
     * 复制附件
     * <p>
     * 当前用户
     *
     * @param businessId     业务id
     * @param businessType
     * @param copyBusinessId 被复制的业务id
     * @return
     */
    @Override
    public int copyAttachmentWithType(String businessId, String businessType, String copyBusinessId) {
        return copyAttachmentWithType(businessId, businessType, copyBusinessId, null);
    }

    /**
     * 复制附件
     * <p>
     * 当前用户
     *
     * @param businessId       业务id
     * @param businessType
     * @param copyBusinessId   被复制的业务id
     * @param copyBusinessType
     * @return
     */
    @Override
    public int copyAttachmentWithType(String businessId, String businessType, String copyBusinessId, String copyBusinessType) {
        return copyAttachmentWithTypeWithSubBusinessId(businessId, null, businessType, copyBusinessId, null, copyBusinessType);
    }

    /**
     * 复制附件
     * <p>
     * 当前用户
     *
     * @param businessId       业务id
     * @param businessType
     * @param copyBusinessId   被复制的业务id
     * @param copyBusinessType
     * @return
     */
    @Override
    public int copyAttachmentWithTypeWithSubBusinessId(String businessId, String subBusinessId, String businessType, String copyBusinessId, String copySubBusinessId, String copyBusinessType) {
        try (SessionFactory factory = new SessionFactory()) {
            factory.setDbEnv(getCurrUser().getCurrDs());
            AttachmentMapper mapper = factory.getMapper(AttachmentMapper.class);
            List<Attachment> attachments = mapper.listByBusinessId(copyBusinessId, subBusinessId, businessType);
            if (CollectionUtils.isEmpty(attachments)) {
                return 0;
            }
            List<Attachment> list = new ArrayList<>();
            for (Attachment attachment : attachments) {
                Attachment copy = new Attachment();
                BeanUtils.copyProperties(copy, attachment);
                StorePath storePath = StorePath.parseFromUrl(attachment.getPath());
                storageClient.downloadFile(storePath.getGroup(), storePath.getPath(), inputStream -> {
                    String url;
                    byte[] bytes = IoUtil.readBytes(inputStream);
                    url = fdfsCient.uploadFile(new ByteArrayInputStream(bytes), bytes.length, attachment.getExtension());
                    copy.setPath(url);
                    copy.setBusinessId(businessId);
                    copy.setType(copyBusinessType);
                    copy.setSubBusinessId(copySubBusinessId);
                    copy.setId(CodeTools.getCode32());
                    list.add(copy);
                    inputStream.close();
                    return null;
                });
            }
            return mapper.batchInsert(list);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage());
        }
    }

    @Override
    public int copyAttachmentWithTypeWithSubBusinessIdWithFileIds(String businessId, List<String> fileIds, String subBusinessId, String businessType, String copyBusinessId, String copySubBusinessId, String copyBusinessType) {
        try (SessionFactory factory = new SessionFactory()) {
            factory.setDbEnv(getCurrUser().getCurrDs());
            AttachmentMapper mapper = factory.getMapper(AttachmentMapper.class);
            List<Attachment> attachments = mapper.listByBusinessId(copyBusinessId, subBusinessId, businessType);
            if (CollectionUtils.isEmpty(attachments)) {
                return 0;
            }
            List<Attachment> list = new ArrayList<>();
            for (Attachment attachment : attachments) {
                if (!fileIds.contains(attachment.getId())) {
                    continue;
                }
                Attachment copy = new Attachment();
                BeanUtils.copyProperties(copy, attachment);
                StorePath storePath = StorePath.parseFromUrl(attachment.getPath());
                storageClient.downloadFile(storePath.getGroup(), storePath.getPath(), inputStream -> {
                    String url;
                    byte[] bytes = IoUtil.readBytes(inputStream);
                    url = fdfsCient.uploadFile(new ByteArrayInputStream(bytes), bytes.length, attachment.getExtension());
                    copy.setPath(url);
                    copy.setBusinessId(businessId);
                    copy.setType(copyBusinessType);
                    copy.setSubBusinessId(copySubBusinessId);
                    copy.setId(CodeTools.getCode32());
                    list.add(copy);
                    inputStream.close();
                    return null;
                });
            }
            return mapper.batchInsert(list);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 下载fdfs文件
     *
     * @param fileUrl 文件路径
     * @return 文件
     */
    @Override
    public byte[] download(String fileUrl) {
        Assert.hasText(fileUrl, "文件路径不能为空");
        return fdfsCient.download(fileUrl);
    }

    @Override
    public int updateAttachment(Attachment attachment) {
        Assert.hasText(attachment.getPath(), "文件路径不能为空");
        return ADOConnection.runTask(ThreadLocalContext.get().getCurrDs(), factory-> {
            try {
                AttachmentMapper mapper = factory.getMapper(AttachmentMapper.class);

                List<Attachment> attachmentList = mapper.listByBusinessId(attachment.getBusinessId(), attachment.getSubBusinessId(), attachment.getType());

                if (CollectionUtils.isEmpty(attachmentList)) {
                    return 0;
                }

                Attachment preAttachment = attachmentList.get(0);
                String prePath = preAttachment.getPath();

                preAttachment.setExtension(attachment.getExtension());
                preAttachment.setType(attachment.getType());
                preAttachment.setSize(attachment.getSize());
                preAttachment.setPath(attachment.getPath());

                int rowNum = mapper.update(preAttachment);

                if (rowNum > 0) {
                    fdfsCient.deleteFile(prePath);
                }
                return rowNum;
            } catch (Exception e) {
                log.error(e.getMessage(), e);
                throw new RuntimeException(e.getMessage());
            }
        },Integer.class);
    }

    /**
     * 生成文件实体
     * <p>
     * 用户
     *
     * @param file       文件
     * @param businessId 业务id
     * @return 附件实体
     */
    private Attachment generateAttachment(MultipartFile file, String businessId, String businessType, String subBusinessId) {

        EamUser user = getCurrUser();

        String originalFilename = file.getOriginalFilename();
        String extension = FilenameUtils.getExtension(originalFilename);
        Attachment attachment = new Attachment();
        attachment.setId(CodeTools.getCode32());
        attachment.setSize((double) file.getSize() / 1024);
        // 防止文件名过长，导致无法入库
        attachment.setName(StringUtils.substring(originalFilename, 0, 1000));
        attachment.setBusinessId(businessId);
        attachment.setExtension(extension);
        attachment.setCreateBy(user.getAccount());
        attachment.setCreateByName(user.getName());
        attachment.setType(businessType);
        attachment.setSubBusinessId(subBusinessId);
        //图片转为base64缩略图存放
        picToBase64(file, extension, attachment);
        return attachment;
    }

    /**
     * 图片转为base64缩略图存放
     * @param file
     * @param extension
     * @param attachment
     */
    private void picToBase64(MultipartFile file, String extension, Attachment attachment) {
        boolean isImage = StrUtil.equalsAnyIgnoreCase(extension, "jpg", "jpeg", "png", "gif", "bmp", "tif", "tiff", "webp", "ico");
        if (!isImage) {
            return;
        }

        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
            Image scaledImage = ImgUtil.scale(bufferedImage, 0.3f);

            // 将Image对象转换为BufferedImage对象
            BufferedImage scaledBufferedImage = new BufferedImage(
                    scaledImage.getWidth(null),
                    scaledImage.getHeight(null),
                    BufferedImage.TYPE_INT_RGB
            );
            Graphics2D g2d = scaledBufferedImage.createGraphics();
            g2d.drawImage(scaledImage, 0, 0, null);
            g2d.dispose();

            ImageIO.write(scaledBufferedImage, "png", outputStream);
            byte[] compressedImageBytes = outputStream.toByteArray();

            String base64 = Base64.encode(compressedImageBytes);
            String imgUrl = StrUtil.format("data:image/{};base64,{}", extension, base64);
            attachment.setImgUrl(imgUrl);
        } catch (IOException e) {
            log.error("图片转base64失败", e.getMessage());
        }
    }

    /**
     * 根据附件id获取附件
     *
     * @param fileIds 附件id
     * @return 附件实体
     */
    @Override
    public List<Attachment> getByIds(String fileIds) {
        try (SessionFactory factory = new SessionFactory()) {
            factory.setDbEnv(getCurrUser().getCurrDs());
            AttachmentMapper mapper = factory.getMapper(AttachmentMapper.class);
            return mapper.getListByIds(fileIds.split(","));
        }
    }

}
